{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "# Siamese Address Similarity with TensorFlow\n",
    "--------------------------\n",
    "\n",
    "Here, we show how to perform address matching with a Siamese RNN model.\n",
    "\n",
    "We start by loading the necessary libraries."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": false,
    "deletable": true,
    "editable": true
   },
   "outputs": [],
   "source": [
    "import os\n",
    "import random\n",
    "import string\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import tensorflow as tf\n",
    "from tensorflow.python.framework import ops\n",
    "ops.reset_default_graph()\n",
    "\n",
    "model = __import__('siamese_similarity_model')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "Start a graph session"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": true,
    "deletable": true,
    "editable": true
   },
   "outputs": [],
   "source": [
    "sess = tf.Session()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "Set the model parameters."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": true,
    "deletable": true,
    "editable": true
   },
   "outputs": [],
   "source": [
    "batch_size = 200     # How many addresses to train on in one batch\n",
    "n_batches = 300      # How many batches to train on\n",
    "max_address_len = 20 # How many character to crop/pad each address\n",
    "margin = 0.25        # A sort of regularization parameter that allows for 'wiggle' room in bad predicted similarities.\n",
    "num_features = 50    # RNN feature size\n",
    "dropout_keep_prob = 0.8 # Dropout probability"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "We need a function to randomly create one typo in a string."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": true,
    "deletable": true,
    "editable": true
   },
   "outputs": [],
   "source": [
    "def create_typo(s):\n",
    "    rand_ind = random.choice(range(len(s)))\n",
    "    s_list = list(s)\n",
    "    s_list[rand_ind]=random.choice(string.ascii_lowercase + '0123456789')\n",
    "    s = ''.join(s_list)\n",
    "    return(s)\n",
    "\n",
    "# Generate data\n",
    "street_names = ['abbey', 'baker', 'canal', 'donner', 'elm', 'fifth',\n",
    "                'grandvia', 'hollywood', 'interstate', 'jay', 'kings']\n",
    "street_types = ['rd', 'st', 'ln', 'pass', 'ave', 'hwy', 'cir', 'dr', 'jct']\n",
    "\n",
    "# Define test addresses\n",
    "test_queries = ['111 abbey ln', '271 doner cicle',\n",
    "                '314 king avenue', 'tensorflow is fun']\n",
    "test_references = ['123 abbey ln', '217 donner cir', '314 kings ave',\n",
    "                   '404 hollywood st', 'tensorflow is so fun']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "Get a batch of size n, half of which is similar addresses, half are not"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": true,
    "deletable": true,
    "editable": true
   },
   "outputs": [],
   "source": [
    "def get_batch(n):\n",
    "    # Generate a list of reference addresses with similar addresses that have\n",
    "    # a typo.\n",
    "    numbers = [random.randint(1, 9999) for i in range(n)]\n",
    "    streets = [random.choice(street_names) for i in range(n)]\n",
    "    street_suffs = [random.choice(street_types) for i in range(n)]\n",
    "    full_streets = [str(w) + ' ' + x + ' ' + y for w,x,y in zip(numbers, streets, street_suffs)]\n",
    "    typo_streets = [create_typo(x) for x in full_streets]\n",
    "    reference = [list(x) for x in zip(full_streets, typo_streets)]\n",
    "    \n",
    "    # Shuffle last half of them for training on dissimilar addresses\n",
    "    half_ix = int(n/2)\n",
    "    bottom_half = reference[half_ix:]\n",
    "    true_address = [x[0] for x in bottom_half]\n",
    "    typo_address = [x[1] for x in bottom_half]\n",
    "    typo_address = list(np.roll(typo_address, 1))\n",
    "    bottom_half = [[x,y] for x,y in zip(true_address, typo_address)]\n",
    "    reference[half_ix:] = bottom_half\n",
    "    \n",
    "    # Get target similarities (1's for similar, -1's for non-similar)\n",
    "    target = [1]*(n-half_ix) + [-1]*half_ix\n",
    "    reference = [[x,y] for x,y in zip(reference, target)]\n",
    "    return(reference)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "Define vocabulary dictionary (remember to save '0' for padding)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": true,
    "deletable": true,
    "editable": true
   },
   "outputs": [],
   "source": [
    "vocab_chars = string.ascii_lowercase + '0123456789 '\n",
    "vocab2ix_dict = {char:(ix+1) for ix, char in enumerate(vocab_chars)}\n",
    "vocab_length = len(vocab_chars) + 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "Define vocab one-hot encoding.  Here we get the actual indices for usage in the embedding lookup."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": true,
    "deletable": true,
    "editable": true
   },
   "outputs": [],
   "source": [
    "def address2onehot(address,\n",
    "                   vocab2ix_dict = vocab2ix_dict,\n",
    "                   max_address_len = max_address_len):\n",
    "    # translate address string into indices\n",
    "    address_ix = [vocab2ix_dict[x] for x in list(address)]\n",
    "\n",
    "    # Pad or crop to max_address_len\n",
    "    address_ix = (address_ix + [0]*max_address_len)[0:max_address_len]\n",
    "    return(address_ix)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "Now we define model placeholders."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": true,
    "deletable": true,
    "editable": true
   },
   "outputs": [],
   "source": [
    "address1_ph = tf.placeholder(tf.int32, [None, max_address_len], name=\"address1_ph\")\n",
    "address2_ph = tf.placeholder(tf.int32, [None, max_address_len], name=\"address2_ph\")\n",
    "\n",
    "y_target_ph = tf.placeholder(tf.int32, [None], name=\"y_target_ph\")\n",
    "dropout_keep_prob_ph = tf.placeholder(tf.float32, name=\"dropout_keep_prob\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "Create embedding lookup. Here we use the identidy matrix so that we have a one-hot encoded lookup."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": true,
    "deletable": true,
    "editable": true
   },
   "outputs": [],
   "source": [
    "identity_mat = tf.diag(tf.ones(shape=[vocab_length]))\n",
    "address1_embed = tf.nn.embedding_lookup(identity_mat, address1_ph)\n",
    "address2_embed = tf.nn.embedding_lookup(identity_mat, address2_ph)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "Now we define the model.  Please see the `06_siamese_similarity_model.py` for details."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": false,
    "deletable": true,
    "editable": true
   },
   "outputs": [],
   "source": [
    "# Define Model\n",
    "text_snn = model.snn(address1_embed, address2_embed, dropout_keep_prob_ph,\n",
    "                     vocab_length, num_features, max_address_len)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "Now we can define our accuracy, loss, and prediction functions.  These come from the `06_siamese_similarity_model.py` model function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": false,
    "deletable": true,
    "editable": true
   },
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "Input 'y' of 'Mul' Op has type int32 that does not match type float32 of argument 'x'.",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "\u001b[1;32m/usr/local/lib/python3.6/site-packages/tensorflow/python/framework/op_def_library.py\u001b[0m in \u001b[0;36mapply_op\u001b[1;34m(self, op_type_name, name, **keywords)\u001b[0m\n\u001b[0;32m    489\u001b[0m                 \u001b[0mas_ref\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0minput_arg\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mis_ref\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 490\u001b[1;33m                 preferred_dtype=default_dtype)\n\u001b[0m\u001b[0;32m    491\u001b[0m           \u001b[1;32mexcept\u001b[0m \u001b[0mTypeError\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0merr\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m/usr/local/lib/python3.6/site-packages/tensorflow/python/framework/ops.py\u001b[0m in \u001b[0;36minternal_convert_to_tensor\u001b[1;34m(value, dtype, name, as_ref, preferred_dtype)\u001b[0m\n\u001b[0;32m    740\u001b[0m         \u001b[1;32mif\u001b[0m \u001b[0mret\u001b[0m \u001b[1;32mis\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 741\u001b[1;33m           \u001b[0mret\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mconversion_func\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mvalue\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mdtype\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mdtype\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mname\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mname\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mas_ref\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mas_ref\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m    742\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m/usr/local/lib/python3.6/site-packages/tensorflow/python/framework/ops.py\u001b[0m in \u001b[0;36m_TensorTensorConversionFunction\u001b[1;34m(t, dtype, name, as_ref)\u001b[0m\n\u001b[0;32m    613\u001b[0m         \u001b[1;34m\"Tensor conversion requested dtype %s for Tensor with dtype %s: %r\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 614\u001b[1;33m         % (dtype.name, t.dtype.name, str(t)))\n\u001b[0m\u001b[0;32m    615\u001b[0m   \u001b[1;32mreturn\u001b[0m \u001b[0mt\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mValueError\u001b[0m: Tensor conversion requested dtype float32 for Tensor with dtype int32: 'Tensor(\"y_target_ph:0\", shape=(?,), dtype=int32)'",
      "\nDuring handling of the above exception, another exception occurred:\n",
      "\u001b[1;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-11-705fe45b778c>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[0;32m      2\u001b[0m \u001b[0mbatch_accuracy\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0maccuracy\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtext_snn\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0my_target_ph\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m      3\u001b[0m \u001b[1;31m# Define Loss\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 4\u001b[1;33m \u001b[0mbatch_loss\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mloss\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtext_snn\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0my_target_ph\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mmargin\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m      5\u001b[0m \u001b[1;31m# Define Predictions\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m      6\u001b[0m \u001b[0mpredictions\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mget_predictions\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtext_snn\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m/home/nick/Documents/tensorflow/book_code/09_Recurrent_Neural_Networks/06_Training_A_Siamese_Similarity_Measure/siamese_similarity_model.py\u001b[0m in \u001b[0;36mloss\u001b[1;34m(scores, y_target, margin)\u001b[0m\n\u001b[0;32m     80\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     81\u001b[0m     \u001b[1;31m# If y-target is -1 to 1, then do the following\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 82\u001b[1;33m     \u001b[0mpos_mult\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mtf\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0madd\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtf\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mmultiply\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m0.5\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0my_target\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m0.5\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     83\u001b[0m     \u001b[1;31m# Else if y-target is 0 to 1, then do the following\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     84\u001b[0m     \u001b[0mpos_mult\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mtf\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcast\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0my_target\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mtf\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mfloat32\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m/usr/local/lib/python3.6/site-packages/tensorflow/python/ops/math_ops.py\u001b[0m in \u001b[0;36mmultiply\u001b[1;34m(x, y, name)\u001b[0m\n\u001b[0;32m    284\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    285\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mmultiply\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mx\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0my\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mname\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mNone\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 286\u001b[1;33m   \u001b[1;32mreturn\u001b[0m \u001b[0mgen_math_ops\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_mul\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mx\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0my\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mname\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m    287\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    288\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m/usr/local/lib/python3.6/site-packages/tensorflow/python/ops/gen_math_ops.py\u001b[0m in \u001b[0;36m_mul\u001b[1;34m(x, y, name)\u001b[0m\n\u001b[0;32m   1375\u001b[0m     \u001b[0mA\u001b[0m\u001b[0;31m \u001b[0m\u001b[0;31m`\u001b[0m\u001b[0mTensor\u001b[0m\u001b[0;31m`\u001b[0m\u001b[1;33m.\u001b[0m \u001b[0mHas\u001b[0m \u001b[0mthe\u001b[0m \u001b[0msame\u001b[0m \u001b[0mtype\u001b[0m \u001b[1;32mas\u001b[0m\u001b[0;31m \u001b[0m\u001b[0;31m`\u001b[0m\u001b[0mx\u001b[0m\u001b[0;31m`\u001b[0m\u001b[1;33m.\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m   1376\u001b[0m   \"\"\"\n\u001b[1;32m-> 1377\u001b[1;33m   \u001b[0mresult\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0m_op_def_lib\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mapply_op\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Mul\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mx\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mx\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0my\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mname\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mname\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m   1378\u001b[0m   \u001b[1;32mreturn\u001b[0m \u001b[0mresult\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m   1379\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m/usr/local/lib/python3.6/site-packages/tensorflow/python/framework/op_def_library.py\u001b[0m in \u001b[0;36mapply_op\u001b[1;34m(self, op_type_name, name, **keywords)\u001b[0m\n\u001b[0;32m    524\u001b[0m                   \u001b[1;34m\"%s type %s of argument '%s'.\"\u001b[0m \u001b[1;33m%\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    525\u001b[0m                   (prefix, dtypes.as_dtype(attrs[input_arg.type_attr]).name,\n\u001b[1;32m--> 526\u001b[1;33m                    inferred_from[input_arg.type_attr]))\n\u001b[0m\u001b[0;32m    527\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    528\u001b[0m           \u001b[0mtypes\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m[\u001b[0m\u001b[0mvalues\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdtype\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mTypeError\u001b[0m: Input 'y' of 'Mul' Op has type int32 that does not match type float32 of argument 'x'."
     ]
    }
   ],
   "source": [
    "# Define Accuracy\n",
    "batch_accuracy = model.accuracy(text_snn, y_target_ph)\n",
    "# Define Loss\n",
    "batch_loss = model.loss(text_snn, y_target_ph, margin)\n",
    "# Define Predictions\n",
    "predictions = model.get_predictions(text_snn)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "We declare our optimizer, training operation, and variable initialization."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "collapsed": true,
    "deletable": true,
    "editable": true
   },
   "outputs": [],
   "source": [
    "# Declare optimizer\n",
    "optimizer = tf.train.AdamOptimizer(0.01)\n",
    "# Apply gradients\n",
    "train_op = optimizer.minimize(batch_loss)\n",
    "\n",
    "# Initialize Variables\n",
    "init = tf.global_variables_initializer()\n",
    "sess.run(init)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "Now we loop through our training iterations and print out convergence statistics every 10 generations."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "collapsed": false,
    "deletable": true,
    "editable": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Training Metrics, Batch 0: Loss=0.572, Accuracy=0.500.\n",
      "Training Metrics, Batch 10: Loss=0.039, Accuracy=0.745.\n",
      "Training Metrics, Batch 20: Loss=0.060, Accuracy=0.745.\n",
      "Training Metrics, Batch 30: Loss=0.043, Accuracy=0.735.\n",
      "Training Metrics, Batch 40: Loss=0.030, Accuracy=0.765.\n",
      "Training Metrics, Batch 50: Loss=0.030, Accuracy=0.710.\n",
      "Training Metrics, Batch 60: Loss=0.013, Accuracy=0.750.\n",
      "Training Metrics, Batch 70: Loss=0.040, Accuracy=0.710.\n",
      "Training Metrics, Batch 80: Loss=0.050, Accuracy=0.770.\n",
      "Training Metrics, Batch 90: Loss=0.002, Accuracy=0.740.\n",
      "Training Metrics, Batch 100: Loss=0.004, Accuracy=0.760.\n",
      "Training Metrics, Batch 110: Loss=0.021, Accuracy=0.750.\n",
      "Training Metrics, Batch 120: Loss=0.013, Accuracy=0.715.\n",
      "Training Metrics, Batch 130: Loss=-0.007, Accuracy=0.780.\n",
      "Training Metrics, Batch 140: Loss=0.049, Accuracy=0.720.\n",
      "Training Metrics, Batch 150: Loss=-0.019, Accuracy=0.800.\n",
      "Training Metrics, Batch 160: Loss=-0.004, Accuracy=0.740.\n",
      "Training Metrics, Batch 170: Loss=0.005, Accuracy=0.765.\n",
      "Training Metrics, Batch 180: Loss=-0.026, Accuracy=0.745.\n",
      "Training Metrics, Batch 190: Loss=-0.022, Accuracy=0.760.\n",
      "Training Metrics, Batch 200: Loss=0.007, Accuracy=0.765.\n",
      "Training Metrics, Batch 210: Loss=-0.001, Accuracy=0.795.\n",
      "Training Metrics, Batch 220: Loss=-0.024, Accuracy=0.760.\n",
      "Training Metrics, Batch 230: Loss=0.016, Accuracy=0.760.\n",
      "Training Metrics, Batch 240: Loss=-0.006, Accuracy=0.770.\n",
      "Training Metrics, Batch 250: Loss=-0.027, Accuracy=0.750.\n",
      "Training Metrics, Batch 260: Loss=-0.001, Accuracy=0.775.\n",
      "Training Metrics, Batch 270: Loss=0.033, Accuracy=0.750.\n",
      "Training Metrics, Batch 280: Loss=0.000, Accuracy=0.760.\n",
      "Training Metrics, Batch 290: Loss=-0.021, Accuracy=0.760.\n"
     ]
    }
   ],
   "source": [
    "# Train loop\n",
    "train_loss_vec = []\n",
    "train_acc_vec = []\n",
    "for b in range(n_batches):\n",
    "    # Get a batch of data\n",
    "    batch_data = get_batch(batch_size)\n",
    "    # Shuffle data\n",
    "    np.random.shuffle(batch_data)\n",
    "    # Parse addresses and targets\n",
    "    input_addresses = [x[0] for x in batch_data]\n",
    "    target_similarity = np.array([x[1] for x in batch_data])\n",
    "    address1 = np.array([address2onehot(x[0]) for x in input_addresses])\n",
    "    address2 = np.array([address2onehot(x[1]) for x in input_addresses])\n",
    "    \n",
    "    train_feed_dict = {address1_ph: address1,\n",
    "                       address2_ph: address2,\n",
    "                       y_target_ph: target_similarity,\n",
    "                       dropout_keep_prob_ph: dropout_keep_prob}\n",
    "\n",
    "    _, train_loss, train_acc = sess.run([train_op, batch_loss, batch_accuracy],\n",
    "                                        feed_dict=train_feed_dict)\n",
    "    # Save train loss and accuracy\n",
    "    train_loss_vec.append(train_loss)\n",
    "    train_acc_vec.append(train_acc)\n",
    "    # Print out statistics\n",
    "    if b%10==0:\n",
    "        print('Training Metrics, Batch {0}: Loss={1:.3f}, Accuracy={2:.3f}.'.format(b, train_loss, train_acc))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "Let us test the algorithm on the test set of addresses."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "collapsed": false,
    "deletable": true,
    "editable": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Query Addresses: ['111 abbey ln', '271 doner cicle', '314 king avenue', 'tensorflow is fun']\n",
      "Model Found Matches: ['123 abbey ln', '217 donner cir', '314 kings ave', 'tensorflow is so fun']\n"
     ]
    }
   ],
   "source": [
    "# Calculate the nearest addresses for test inputs\n",
    "# First process the test_queries and test_references\n",
    "test_queries_ix = np.array([address2onehot(x) for x in test_queries])\n",
    "test_references_ix = np.array([address2onehot(x) for x in test_references])\n",
    "num_refs = test_references_ix.shape[0]\n",
    "best_fit_refs = []\n",
    "for query in test_queries_ix:\n",
    "    test_query = np.repeat(np.array([query]), num_refs, axis=0)\n",
    "    test_feed_dict = {address1_ph: test_query,\n",
    "                      address2_ph: test_references_ix,\n",
    "                      y_target_ph: target_similarity,\n",
    "                      dropout_keep_prob_ph: 1.0}\n",
    "    test_out = sess.run(text_snn, feed_dict=test_feed_dict)\n",
    "    best_fit = test_references[np.argmax(test_out)]\n",
    "    best_fit_refs.append(best_fit)\n",
    "\n",
    "print('Query Addresses: {}'.format(test_queries))\n",
    "print('Model Found Matches: {}'.format(best_fit_refs))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "deletable": true,
    "editable": true
   },
   "source": [
    "This is great! The model generalizes to strings that aren't even addresses!!\n",
    "\n",
    "Now let us plot the loss and accuracy."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "collapsed": false,
    "deletable": true,
    "editable": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "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\n6MB5JSwszGsaAAyAy5Yty/fddx/v27fPbdp9+/Zx48aNuXXr1tbjzN+kSZOsyxs3bmRm5uPHjzuk\nsT8uPDz8X9UlJSXFmlfdunWty23atMlVvp74/vvvrfma/PTTTzm25RZfrkthsXbt2lzVryDq8tdf\nf/Gtt97KBw4cyPe87fNMSkrK9/x9oWzZstblNWvW8C233OIx/YkTJ7hVq1Ye0yQlJfGtt97K27dv\n/9d55ReZmZkcExPDWVlZuTrOl+vi6t4AsIN9aGPVUnBDrVq18NNPP6FVq1Zu07Rq1QpHjx7FqFGj\ncuybNGmSdfn222/HCy+8gJ07dwIA6tatC8BxEj7nUU/Lli3DjBkzfJbXtBKcl01LISYmBlOnTsXy\n5cuRnZ3tcCwzY+vWrS6H4drjahjcoUOHrMtz584ttB61xWLBkSNH/tUX9C5evIgWLVrgs88+s24L\nxNlzu3fvjt9//x1PPPGEX8upUKECiCjff7khKSkJlStXBiCu127duuG6665D69at8csvvwAARo0a\nhWPHjqFdu3YYMWIEAGDy5Mlo3bo12rZt6/B8Ll68GJ07d8Y111zj4Gb1RkREBG644Qa0adMG999/\nv/W5+vzzz9GyZUu0adMGjz76KADg999/R7t27dCuXTu0b9/erQv3xIkTOHnypMPzGhD4ojkC6VdQ\nlkKLFi18zjc8PDyHpeD8K1u2LI8cOZIB8OjRo7lUqVIO+x9//HE+cuQIMzOnp6dbt58+fdqnuqxb\nt85lueXKlWNm5tdff926berUqbx48WJOTU1lZubp06czAK5Xrx5HRka6reeoUaOseWRmZjIz82uv\nveZQXsuWLX0+b+7qwsycnZ2dqzwmTpzIALh79+6ckpKSaxmYmb/66itrPTIyMpiZ+Z133gk4S8GU\np0KFCvmet30P09s9ndefN4KCgrht27bcrFkzrlChAu/YsYOZpWedmJjIzMwxMTHcuHFjtlgsOXr3\nq1at4i5duljv77i4OKul8NprrzEz88qVK7lbt245ynZnKbRu3dpqzb/99ts8ZMgQZmauXbs2p6en\nMzNzfHw8WywW7tmzJ69evZqZmZOTk63PijPbt2/n7du3886dO72eE3vUUihg1q1bhyZNmjjMb+QN\nT9bEqVOnUKNGDaSmpmL+/PkAgOuuuy7H+wzz5s1DmzZtsGbNGgefvWld2JOdnW2d0dXk9OnTLstP\n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\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x7f9ed4d466a0>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Plot the loss and accuracy\n",
    "%matplotlib inline\n",
    "plt.plot(train_loss_vec, 'k-', lw=2, label='Batch Loss')\n",
    "plt.plot(train_acc_vec, 'r:', label='Batch Accuracy')\n",
    "plt.xlabel('Iterations')\n",
    "plt.ylabel('Accuracy and Loss')\n",
    "plt.title('Accuracy and Loss of Siamese RNN')\n",
    "plt.grid()\n",
    "plt.legend(loc='lower right')\n",
    "plt.show()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
